﻿using MultiLang;
using System;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;
using JetBrains.Annotations;
using Rsdn.TreeGrid;
using System.Collections.Generic;

namespace Rsdn.Janus.ObjectModel
{
    /// <summary>
    /// Список возможностей. Например, форумы, поиск, outbox.
    /// </summary>
    public sealed class Features : ITreeNode, IDisposable
    {
        private IFeature _oldFeature;
        private IFeature _activeFeature;

        private NodeFlags flags;

        private readonly ArrayList _features = new ArrayList(20);

        #region События.

        public delegate void BeforeFeatureActivateHandler(
            IFeature oldFeature, IFeature newFeature, ref bool cancel);

        public delegate void AfterFeatureActivateHandler(IFeature oldFeature, IFeature newFeature);

        ///<summary>Вызывается перед изменением активной фичи.</summary>
        /// <returns>если установить в true активация была отклонена.</returns>
        public event BeforeFeatureActivateHandler OnBeforeFeatureActivate;

        ///<summary>Вызывается после изменения содержимого одного из форумов.</summary>
        public event AfterFeatureActivateHandler OnAfterFeatureActivate;
        #endregion

        #region Features image indexes
        private int outboxImageIndex;
        private int forumsImageIndex;
        private int favoritesImageIndex;
        private int searchImageIndex;
        private int unsubscribedFolderImageIndex;
        private int answersMeImageIndex;
        #endregion

        #region Constructor(s) & Реализация Singleton

        private static readonly Features _instance = new Features();

        private Features()
        {
        }

        public static Features Instance
        {
            get { return _instance; }
        }

        #endregion

        public void Init()
        {
            IServiceProvider serviceProvider = ApplicationManager.Instance.ServiceProvider;

            // Note - *** here is additional entry for complete tree
            Add(DraftboxFeature.Instance);
            Add(new OutboxFeature(serviceProvider));
            Add(Forums.Instance); // answersMe and myAnswers is virtual forums folders
            Add(SearchFeature.Instance);
            Add(FavoritesFeature.Instance);
        }

        public void Refresh()
        {
#if FRM_UPDATE_TRACE
            Trace.WriteLine("FRU: Features.Refresh");                 //MLHIDE
#endif
            //_activeFeature = null; // Note AN : why it was?
            if (_activeFeature is Forum)
            {
                Forum activeForum = _activeFeature as Forum;
                IMsg activeMsg = activeForum.ActiveMsg;
                int id = -1;

                if (activeMsg != null)
                {
                    id = activeMsg.ID;
                }
                //activeForum.Parent;
                activeForum.Refresh();
                IFeature savedActiveFeature = ActiveFeature;
                ActiveFeature = null;

                // It could be a trouble when forum is deleted from the list
                // set feature active only if it is inside inbox folder
                if (savedActiveFeature != null)
                {
                    IFeature findFeatureByKey = FindFeatureByKey(savedActiveFeature.Key);
                    if (findFeatureByKey != null)
                    {
                        ActiveFeature = findFeatureByKey;


                        if (id > 0)
                        {
                            // force read messages
                            IMsg msgs = activeForum.Msgs;

                            // find message and reselect it;
                            IMsg msg = activeForum.FindMsgById(id);
                            activeForum.ActiveMsg = msg;
                        }
                    }
                }
            }
            else if (_activeFeature != null)
            {
                _activeFeature.Refresh();
            }

#if FRM_UPDATE_TRACE
            Trace.WriteLine("FRU: ---Features.Refresh refresh all features---"); //MLHIDE
#endif

            // refresh all features
            foreach (IFeature feature in _features)
            {
                if (feature != _activeFeature)
                {
                    feature.Refresh();
                }
            }
        }

        public void RefreshSelection()
        {
#if FRM_UPDATE_TRACE
            Trace.WriteLine("FRU: Features.RefreshSelection");        //MLHIDE
#endif
            if (_activeFeature is Forum)
            {
                Forum activeForum = _activeFeature as Forum;
                IMsg activeMsg = activeForum.ActiveMsg;
                int id = -1;

                if (activeMsg != null)
                {
                    id = activeMsg.ID;
                }

                if (id > 0)
                {
                    // force read messages
                    IMsg msgs = activeForum.Msgs;

                    // find message and reselect it;
                    IMsg msg = activeForum.FindMsgById(id);
                    activeForum.ActiveMsg = msg;

                    // force message selection in the viewer
                    IFeatureGui featureGui = ActiveFeature as IFeatureGui;
                    if (featureGui != null)
                    {
                        Control associatedControl = featureGui.GuiControl;
                        ForumDummyFormBase forumDummyFormBase = associatedControl as ForumDummyFormBase;
                        if (forumDummyFormBase != null)
                        {
                            //ApplicationManager.Instance.ForumNavigator.ViewHistory.CurrentEntry = null;
                            forumDummyFormBase.SelectMessage(msg, true);
                        }
                    }
                }

            }
        }

        /// <summary>
        /// Gets or sets the active feature.
        /// </summary>
        /// <value>The active feature.</value>
        /// <exception cref="ApplicationException"><c>ApplicationException</c>.</exception>
        [CanBeNull]
        public IFeature ActiveFeature
        {
            get
            {
                if (_activeFeature == null)
                {
                    lock (typeof(Features))
                    {
                        if (_activeFeature == null)
                        {
                            string key = Config.Instance.ActiveFeature;
                            if (key.Length != 0)
                            {
                                return FindFeatureByKey(key);
                            }
                        }
                    }
                }

                return _activeFeature;
            }

            set
            {
                lock (typeof(Features))
                {
                    if (_activeFeature == value)
                    {
                        return;
                    }

                    if (value == null)
                    {
                        _activeFeature = null;
                        Config.Instance.ActiveFeature = string.Empty;
                    }
                    else
                    {
                        IFeature newValue = value;
                        string key = value.Key;
                        if (!FeatureExists(_features, value))
                        {
                            newValue = FindFeatureByKey(value.Key);
                            if (newValue == null)
                            {
                                throw new ApplicationException(
                                    String.Format(ml.ml_string(245, "Feature '{0}' отсутствует в коллекции feature. (v.2)"), newValue));
                            }
                        }

                        bool cancel = InvokeBeforeFeatureActivate(_activeFeature, newValue);
                        if (!cancel)
                        {
                            _activeFeature = newValue;
                            Config.Instance.ActiveFeature = newValue.Key;
                            InvokeAfterFeatureActivate(_oldFeature, newValue);

                            // Prevent loss of old feature while refresh
                            _oldFeature = _activeFeature;
                        }
                    }
                }
            }
        }

        internal void FeatureChanged(IFeature changedFeature)
        {
#if DEBUG
            //if(!FeatureExists(_features, changedFeature))
            //	throw new ApplicationException("Feature '" + changedFeature
			//		+ "' отсутствует в коллекции feature.");
#endif //DEBUG
            OnAfterFeatureChanged(changedFeature);
        }

        public IFeature[] GetAllFeatures()
        {
            var list = new List<IFeature>(100);
            GetAllFeatures(_features, list);
            return list.ToArray();
        }

        private static void GetAllFeatures(ICollection features, IList list)
        {
            foreach (IFeature feature in features)
            {
                list.Add(feature);
                GetAllFeatures(feature, list);
            }
        }

        private static bool FeatureExists(ICollection features, IFeature lookedFor)
        {
            foreach (IFeature feature in features)
            {
                if (feature == lookedFor)
                {
                    return true;
                }

                if (FeatureExists(feature, lookedFor))
                {
                    return true;
                }
            }

            return false;
        }

        public IFeature FindFeatureByKey(string key)
        {
            var feature = FindFeatureByKey(_features, key);
            return feature;
        }

        private static IFeature FindFeatureByKey(ICollection features, string key)
        {
            IFeature ret = null;
            foreach (IFeature feature in features)
            {
                if (feature.Key == key)
                {
                    return feature;
                }

                ret = FindFeatureByKey(feature, key);
                if (ret != null)
                {
                    break;
                }
            }

            return ret;
        }


        public void ConfigChanged()
        {
            foreach (IFeature feature in _features)
            {
                FeatureConfigChanged(feature);
            }
        }

        private static void FeatureConfigChanged(IFeature feature)
        {
            foreach (IFeature subFeature in feature)
            {
                FeatureConfigChanged(subFeature);
            }
            var featureGui = feature as IFeatureGui;
            if (featureGui != null)
            {
                featureGui.ConfigChanged();
            }
        }
        
        // IDisposable
        public void Dispose()
        {
            foreach (IFeature feature in _features)
            {
                DisposeFeature(feature);
            }
        }

        private static void DisposeFeature(IFeature feature)
        {
            foreach (IFeature subFeature in feature)
            {
                DisposeFeature(subFeature);
            }

            var disposable = feature as IDisposable;
            if (disposable != null)
            {
                disposable.Dispose();
            }
        }

        private IFeature this[int index]
        {
            get
            {
                return (IFeature)_features[index];
            }
        }

        ITreeNode ITreeNode.this[int id]
        {
            get
            {
                return this[id];
            }
        }

        NodeFlags ITreeNode.Flags
        {
            get
            {
                return flags;
            }

            set
            {
                flags = value;
            }
        }

        bool ITreeNode.HasChildren
        {
            get
            {
                return _features.Count > 0;
            }
        }

        ITreeNode ITreeNode.Parent
        {
            get
            {
                return null;
            }
        }

        private void Add(IFeature feature)
        {
            _features.Add(feature);
            ((Feature)feature)._parent = this;
        }

        // ICollection
        public int Count
        {
            get
            {
                return _features.Count;
            }
        }

        public bool IsSynchronized
        {
            get
            {
                return _features.IsSynchronized;
            }
        }

        public object SyncRoot
        {
            get
            {
                return _features.SyncRoot;
            }
        }

        public void CopyTo(Array array, int index)
        {
            _features.CopyTo(array, index);
        }

        public IEnumerator GetEnumerator()
        {
            return _features.GetEnumerator();
        }

        #region Events invocators.

        ///<summary>Хэлпер для вызова события BeforeFeatureActivate.</summary>
        /// <param name="oldFeature">Старая активная фича.</param>
        /// <param name="newFeature">Активизируемая фича.</param>
        /// <returns>true - если активация была отклонена.</returns>
        private bool InvokeBeforeFeatureActivate(IFeature oldFeature, IFeature newFeature)
        {
            var cancel = false;
            if (OnBeforeFeatureActivate != null)
            {
                OnBeforeFeatureActivate(oldFeature, newFeature, ref cancel);
            }

            return cancel;
        }

        ///<summary>Хэлпер для вызова события AfterFeatureActivate.</summary>
        /// <param name="oldFeature">Старая активная фича.</param>
        /// <param name="newFeature">Активируемая фича.</param>
        private void InvokeAfterFeatureActivate(IFeature oldFeature, IFeature newFeature)
        {
            if (OnAfterFeatureActivate != null)
            {
                OnAfterFeatureActivate(oldFeature, newFeature);
            }
        }

        public delegate void FeatureChangedHandler(IFeature changedFeature);

        ///<summary>Вызывается перед изменением активной фичи.</summary>
        public event FeatureChangedHandler AfterFeatureChanged;

        private void OnAfterFeatureChanged(IFeature changedFeature)
        {
            if (AfterFeatureChanged != null)
            {
                AfterFeatureChanged(changedFeature);
            }
        }

        #endregion События.

        #region Features image indexes

        public int OutboxImageIndex
        {
            get { return outboxImageIndex; }
            set { outboxImageIndex = value; }
        }

        public int ForumsImageIndex
        {
            get { return forumsImageIndex; }
            set { forumsImageIndex = value; }
        }

        public int FavoritesImageIndex
        {
            get { return favoritesImageIndex; }
            set { favoritesImageIndex = value; }
        }

        public int SearchImageIndex
        {
            get { return searchImageIndex; }
            set { searchImageIndex = value; }
        }

        public int UnsubscribedFolderImageIndex
        {
            get { return unsubscribedFolderImageIndex; }
            set { unsubscribedFolderImageIndex = value; }
        }

        public int AnswersMeImageIndex
        {
            get { return answersMeImageIndex; }
            set { answersMeImageIndex = value; }
        }

        #endregion
    }
}